ヘッドレス CMS の Contentful のマークダウンテキストに unist を使ってショートコード的な機能を実装してみる
どうも、ベルリンオフィスの小西です。
ヘッドレス CMS の Contentful ではリッチテキスト形式、マークダウン形式など様々なエディターが用意されていますが、特定のテキスト入力をフロント側でカスタマイズしたいケースがあると思います。
イメージとしては WordPress のショートコード的な機能が近いのですが、今回それを Contentful + Gatsby で実装する方法を紹介します。
今回やりたいこと
例えば Contentuful 側のマークダウンエディターで入力された下記のテキストがあるとします。
念願だった[color="#e24040"]マッターホルン[/color]を歩いてきました。
そのまま Contentful 上のプレビューだと↓のような表示になります(当たり前ですが)。
それをフロント側で↓の表示ができるようにします。
前提
今回の機能は gatsby-transformer-remark
の子プラグインとして実装しますので、下記のパッケージが必要になります。Gatsby は 4系でも問題ないかと思います。
- "gatsby": "^5.3.3",
- "gatsby-source-contentful": "^8.3.1",
- "gatsby-transformer-remark": "^6.3.2",
- "react": "^18.2.0",
使用する関連プラグインの理解
作業に着手する前に、今回使用する関連の Gatsby プラグインについて軽く触れておきたいと思います。さっさと実装したいという方は次の「自前プラグインの実装」まで飛んでいただいて大丈夫です。
gatsby-transformer-remark
は remark を利用するための Gatsby オフィシャルのパッケージです。
remark は JavaScript でマークダウンテキストを扱うパッケージですが、特徴としてはマークダウンをいったん AST (Abstract Syntax Trees)と呼ばれるツリー状のオブジェクトに変換して、さまざまな処理(例えばマークダウンを HTML に、など)を行いやすくしてくれます。
AST例:
"htmlAst": { "type": "root", "children": [ { "type": "element", "tagName": "p", "properties": {}, "children": [ { "type": "text", "value": "念願だった[color=\"#e24040\"]マッターホルン[/color]を歩いてきました。" } ] }, ...
gatsby-transformer-remark
と gatsby-source-contentful
を併用することで、ソースデータとして Contentful から素のマークダウン形式で渡ってくるデータを AST に変換して、色々いじりやすくしてくれてるわけです。
そもそも Gatsby のパッケージには大きく分けて2つあり、ソースの取り込みとデータ変換を目的とするものがあります。
「gatsby-source-
」から始まるソースプラグイン
ある特定のデータ群を Gatsby のノードに変換して、ページ生成のソースに使えるようにするためのものです。API を通じた外部ソースやローカルの内部ファイルからデータを収集できます。
例えば Shopify からデータを取り込む gatsby-source-shopify
, ローカルのファイル群をソース化する gatsby-source-filesystem
などがあります。
「gatsby-transformer-
」から始まる変換プラグイン
ソースプラグインから提供されたデータを、新しいノードやノードフィールドに「変換」して使いやすくします。
例えば gatsby-transformer-json
では生の JSON ファイルから JavaScript オブジェクトに変換したり、 gatsby-transformer-sharp
では ImageSharp
というノードに変換することで GraphQL クエリを通じて画像変換をかけられるようになります。
今回制作するプラグインもテキストの「変換」処理を行うためのものなので、「gatsby-transformer-
」の命名に沿って実装したいと思います。
自前プラグインの実装
では、テキストのカスタマイズを実現するプラグインを作っていきます。
1. 自前プラグインの初期化とインストール
Gatsby アプリケーションのルートディレクトリ( src
と同じ階層)に plugins
というディレクトリを作成し、さらにその中に今回のパッケージ gatsby-remark-textcolor
を作成します。
$ cd ~/Gatsbyアプリのルートディレクトリ $ mkdir -p plugins/gatsby-remark-textcolor $ cd plugins/gatsby-remark-textcolor
パッケージを初期化して package.json を生成します。色々聞かれますが、そのままでOKです。
$ npm init package name: (gatsby-remark-textcolor) version: (1.0.0) description: entry point: (index.js) test command: git repository: keywords: author: license: (ISC) Is this OK? (yes)
Contentful から渡ってくるデータソースのノードに対して変更を加えるため、パッケージをインストールします。
(注: unist-util-visit
の新しいメジャーバージョンは ESM であり、Gatsby でまだ完全にサポートされていないため、v2 をインストールする必要があります)
$ npm install unist-util-visit@^2
unist (Universal Syntax Tree)はシンタックスツリーの仕様で、複雑なコードを書かずに AST を扱えるようになります。 unist-util-*
という接頭辞のついたオープンソースのパッケージが多数公開されています。
今回使用する unist-util-visit
は、変換するノードを特定する関数、AST を変換する関数などを提供してくれます。
その後、プラグイン起動に必要なファイルのみ残しておきます。
$ rm -r node_modules package-lock.json
2. 実装処理
plugins/gatsby-remark-textcolor/index.js
const visit = require("unist-util-visit"); module.exports = ({ markdownAST }, pluginOptions) => { const regexMatch = /\[color="(#(?:[0-9a-fA-F]{3}){1,2})"\](.+?)\[\/color\]/g; visit(markdownAST, "text", (node) => { node.type = "html" node.value = node.value.replace(regexMatch, '<span style="color: $1">$2</span>'); }) return markdownAST }
visit
では AST を探索(各ノードにそれぞれアクセス)し、記述された処理で変換した値を返します。
visit(tree[, test], visitor[, reverse])
今回のコードでは、markdownAST 内の text
ノードをフィルターし、正規表現にマッチする文字列を置換しています。
また HTML タグを認識させるため、node.type
を text
から html
に置き換えています。
なお、pluginOptions
は後述の gatsby-config.js
で指定するオプション値を受け取れます。
3. アプリケーションへ自前プラグインのインストール
アプリケーションのルートディレクトリに戻り、 gatsby-config.js
を開きます。
gatsby-transformer-remark
内のプラグインとしてインストールします。
{ resolve: `gatsby-transformer-remark`, options: { plugins: [ `gatsby-remark-textcolor`, ], }, }, ...
オプションを指定する場合は下記のような記述になります。先のpluginOptions
で受け取れます。
{ resolve: `gatsby-transformer-remark`, options: { plugins: [ { resolve: `gatsby-remark-textcolor`, options: { backgroundColor: true, //あくまで例です。 }, }, ...
4. 出来栄え
以上で準備が完了したのでアプリを立ち上げます。
$ gatsby develop
できました!
最後に
以上、仕組みをわかっていれば比較的簡単にショートコードを実装することができました。
例えば Contetnful 文中の画像を 自動でデバイス最適化してくれる gatsby-remark-images-contentful
も同じ仕組みで、パッケージの中身を見てみると、Contentful の画像パスを含むノードを、最適化された画像 HTML タグに書き換えてくれていたりします。
クラスメソッドでは Contentful, Jamstack のご相談、運用支援を承っています。
ご興味のある方はぜひお問い合わせください。